include $(TOPDIR)/rules.mk
PKG_NAME:=dufs
-PKG_VERSION:=0.44.0
-PKG_RELEASE:=2
+PKG_VERSION:=0.45.0
+PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/sigoden/dufs/tar.gz/v$(PKG_VERSION)?
-PKG_HASH:=940fe767946699bdecb9be24700f9abe5a08e913ff7edf1a5388c8a540ff1e0f
+PKG_HASH:=62aa2cadd77e1bd9d96c77cbd832a53ffc364301c549001bf8fd9d023cbd8ab1
PKG_LICENSE:=Apache-2.0 MIT
+++ /dev/null
-From b2f244a4cfeb492b38ad9b92692e230e04540ea0 Mon Sep 17 00:00:00 2001
-Date: Sat, 16 Aug 2025 07:36:19 +0800
-Subject: [PATCH] feat: make dir urls inherit `?noscript` params (#614)
-
----
- src/noscript.rs | 13 ++++++++-----
- 1 file changed, 8 insertions(+), 5 deletions(-)
-
---- a/src/noscript.rs
-+++ b/src/noscript.rs
-@@ -55,17 +55,20 @@ pub fn generate_noscript_html(data: &Ind
-
- fn render_parent() -> String {
- let value = "../";
-- format!("<tr><td><a href=\"{value}\">{value}</a></td><td></td><td></td></tr>")
-+ format!("<tr><td><a href=\"{value}?noscript\">{value}</a></td><td></td><td></td></tr>")
- }
-
- fn render_path_item(path: &PathItem) -> String {
-- let href = encode_uri(&path.name);
-- let suffix = if path.path_type.is_dir() { "/" } else { "" };
-- let name = escape_str_pcdata(&path.name);
-+ let mut href = encode_uri(&path.name);
-+ let mut name = escape_str_pcdata(&path.name).to_string();
-+ if path.path_type.is_dir() {
-+ href.push_str("/?noscript");
-+ name.push('/');
-+ };
- let mtime = format_mtime(path.mtime).unwrap_or_default();
- let size = format_size(path.size, path.path_type);
-
-- format!("<tr><td><a href=\"{href}{suffix}\">{name}{suffix}</a></td><td>{mtime}</td><td>{size}</td></tr>")
-+ format!("<tr><td><a href=\"{href}\">{name}</a></td><td>{mtime}</td><td>{size}</td></tr>")
- }
-
- fn format_mtime(mtime: u64) -> Option<String> {
+++ /dev/null
-From f8a7873582567a85095ca9d2124b185cd3eb2ffd Mon Sep 17 00:00:00 2001
-Date: Tue, 19 Aug 2025 07:51:52 +0800
-Subject: [PATCH] fix: perms on `dufs -A -a @/:ro` (#619)
-
----
- src/auth.rs | 9 ++++++---
- src/server.rs | 4 ++--
- tests/auth.rs | 18 ++++++++++++++++++
- 3 files changed, 26 insertions(+), 5 deletions(-)
-
---- a/src/auth.rs
-+++ b/src/auth.rs
-@@ -30,6 +30,7 @@ lazy_static! {
-
- #[derive(Debug, Clone, PartialEq)]
- pub struct AccessControl {
-+ empty: bool,
- use_hashed_password: bool,
- users: IndexMap<String, (String, AccessPaths)>,
- anonymous: Option<AccessPaths>,
-@@ -38,6 +39,7 @@ pub struct AccessControl {
- impl Default for AccessControl {
- fn default() -> Self {
- AccessControl {
-+ empty: true,
- use_hashed_password: false,
- users: IndexMap::new(),
- anonymous: Some(AccessPaths::new(AccessPerm::ReadWrite)),
-@@ -48,7 +50,7 @@ impl Default for AccessControl {
- impl AccessControl {
- pub fn new(raw_rules: &[&str]) -> Result<Self> {
- if raw_rules.is_empty() {
-- return Ok(Default::default());
-+ return Ok(Self::default());
- }
- let new_raw_rules = split_rules(raw_rules);
- let mut use_hashed_password = false;
-@@ -93,13 +95,14 @@ impl AccessControl {
- }
-
- Ok(Self {
-+ empty: false,
- use_hashed_password,
- users,
- anonymous,
- })
- }
-
-- pub fn exist(&self) -> bool {
-+ pub fn has_users(&self) -> bool {
- !self.users.is_empty()
- }
-
-@@ -111,7 +114,7 @@ impl AccessControl {
- token: Option<&String>,
- guard_options: bool,
- ) -> (Option<String>, Option<AccessPaths>) {
-- if self.users.is_empty() {
-+ if self.empty {
- return (None, Some(AccessPaths::new(AccessPerm::ReadWrite)));
- }
-
---- a/src/server.rs
-+++ b/src/server.rs
-@@ -962,7 +962,7 @@ impl Server {
- uri_prefix: self.args.uri_prefix.clone(),
- allow_upload: self.args.allow_upload,
- allow_delete: self.args.allow_delete,
-- auth: self.args.auth.exist(),
-+ auth: self.args.auth.has_users(),
- user,
- editable,
- };
-@@ -1226,7 +1226,7 @@ impl Server {
- allow_search: self.args.allow_search,
- allow_archive: self.args.allow_archive,
- dir_exists: exist,
-- auth: self.args.auth.exist(),
-+ auth: self.args.auth.has_users(),
- user,
- paths,
- };
---- a/tests/auth.rs
-+++ b/tests/auth.rs
-@@ -126,6 +126,24 @@ fn auth_skip_if_no_auth_user(server: Tes
- }
-
- #[rstest]
-+fn auth_no_skip_if_anonymous(
-+ #[with(&["--auth", "@/:ro"])] server: TestServer,
-+) -> Result<(), Error> {
-+ let url = format!("{}index.html", server.url());
-+ let resp = fetch!(b"GET", &url)
-+ .basic_auth("user", Some("pass"))
-+ .send()?;
-+ assert_eq!(resp.status(), 401);
-+ let resp = fetch!(b"GET", &url).send()?;
-+ assert_eq!(resp.status(), 200);
-+ let resp = fetch!(b"DELETE", &url)
-+ .basic_auth("user", Some("pass"))
-+ .send()?;
-+ assert_eq!(resp.status(), 401);
-+ Ok(())
-+}
-+
-+#[rstest]
- fn auth_check(
- #[with(&["--auth", "user:pass@/:rw", "--auth", "user2:pass2@/", "-A"])] server: TestServer,
- ) -> Result<(), Error> {
+++ /dev/null
-From 4016715187db5bd84c7d15ea6abcd99fd4a0a667 Mon Sep 17 00:00:00 2001
-Date: Tue, 19 Aug 2025 08:58:59 +0800
-Subject: [PATCH] fix: login btn does not work for readonly annoymous (#620)
-
----
- assets/index.js | 7 ++++---
- src/server.rs | 13 ++++++++++++-
- tests/auth.rs | 16 ++++++++++++++--
- 3 files changed, 30 insertions(+), 6 deletions(-)
-
---- a/assets/index.js
-+++ b/assets/index.js
-@@ -534,7 +534,7 @@ async function setupAuth() {
- $loginBtn.classList.remove("hidden");
- $loginBtn.addEventListener("click", async () => {
- try {
-- await checkAuth();
-+ await checkAuth("login");
- } catch { }
- location.reload();
- });
-@@ -782,9 +782,10 @@ async function saveChange() {
- }
- }
-
--async function checkAuth() {
-+async function checkAuth(variant) {
- if (!DATA.auth) return;
-- const res = await fetch(baseUrl(), {
-+ const qs = variant ? `?${variant}` : "";
-+ const res = await fetch(baseUrl() + qs, {
- method: "CHECKAUTH",
- });
- await assertResOK(res);
---- a/src/server.rs
-+++ b/src/server.rs
-@@ -211,7 +211,18 @@ impl Server {
- }
-
- if method.as_str() == "CHECKAUTH" {
-- *res.body_mut() = body_full(user.clone().unwrap_or_default());
-+ match user.clone() {
-+ Some(user) => {
-+ *res.body_mut() = body_full(user);
-+ }
-+ None => {
-+ if has_query_flag(&query_params, "login") || !access_paths.perm().readwrite() {
-+ self.auth_reject(&mut res)?
-+ } else {
-+ *res.body_mut() = body_full("");
-+ }
-+ }
-+ }
- return Ok(res);
- } else if method.as_str() == "LOGOUT" {
- self.auth_reject(&mut res)?;
---- a/tests/auth.rs
-+++ b/tests/auth.rs
-@@ -147,7 +147,7 @@ fn auth_no_skip_if_anonymous(
- fn auth_check(
- #[with(&["--auth", "user:pass@/:rw", "--auth", "user2:pass2@/", "-A"])] server: TestServer,
- ) -> Result<(), Error> {
-- let url = format!("{}index.html", server.url());
-+ let url = format!("{}", server.url());
- let resp = fetch!(b"CHECKAUTH", &url).send()?;
- assert_eq!(resp.status(), 401);
- let resp = send_with_digest_auth(fetch!(b"CHECKAUTH", &url), "user", "pass")?;
-@@ -161,7 +161,7 @@ fn auth_check(
- fn auth_check2(
- #[with(&["--auth", "user:pass@/:rw|user2:pass2@/", "-A"])] server: TestServer,
- ) -> Result<(), Error> {
-- let url = format!("{}index.html", server.url());
-+ let url = format!("{}", server.url());
- let resp = fetch!(b"CHECKAUTH", &url).send()?;
- assert_eq!(resp.status(), 401);
- let resp = send_with_digest_auth(fetch!(b"CHECKAUTH", &url), "user", "pass")?;
-@@ -171,6 +171,18 @@ fn auth_check2(
- Ok(())
- }
-
-+#[rstest]
-+fn auth_check3(
-+ #[with(&["--auth", "user:pass@/:rw", "--auth", "@/dir1:rw", "-A"])] server: TestServer,
-+) -> Result<(), Error> {
-+ let url = format!("{}dir1/", server.url());
-+ let resp = fetch!(b"CHECKAUTH", &url).send()?;
-+ assert_eq!(resp.status(), 200);
-+ let resp = fetch!(b"CHECKAUTH", format!("{url}?login")).send()?;
-+ assert_eq!(resp.status(), 401);
-+ Ok(())
-+}
-+
- #[rstest]
- fn auth_logout(
- #[with(&["--auth", "user:pass@/:rw", "-A"])] server: TestServer,